package com.laolang.thresh.framework.web.interceptor;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.laolang.thresh.framework.redis.util.RedisUtil;
import com.laolang.thresh.framework.web.annotations.AnonymousAccess;
import com.laolang.thresh.module.auth.business.TokenBusiness;
import com.laolang.thresh.module.auth.consts.AuthConsts;
import com.laolang.thresh.module.auth.consts.AuthRedisConsts;
import com.laolang.thresh.module.auth.dto.LoginUser;
import com.laolang.thresh.module.auth.exception.AuthBusinessException;
import com.laolang.thresh.module.auth.propterties.AuthProperties;
import com.laolang.thresh.module.system.user.business.SysUserBusiness;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * jwt 拦截器.
 *
 * @author laolang
 * @version 0.1
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class AuthInterceptor implements HandlerInterceptor {

    private final AuthProperties authProperties;
    private final TokenBusiness tokenBusiness;
    private final SysUserBusiness sysUserBusiness;
    private final RedisUtil redisUtil;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
        log.info("thresh spring boot jwt interceptor");

        if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
            return true;
        }

        // 不处理登录,注册相关请求
        if (matchIgnoreUrls(request)) {
            return true;
        }

        // 匿名访问处理
        if (anonymouseHandler(request, (HandlerMethod) handler)) {
            return true;
        }

        // 非匿名访问处理
        // 获取 token
        String token = request.getHeader(authProperties.getHeader());
        log.info("token:{}", token);
        // token 为空或格式不正确
        if (StrUtil.isBlank(token) || token.length() <= 7) {
            log.error("token 格式不正确:【{}】", token);
            throw AuthBusinessException.authTokenVerifyFailed();
        }
        token = token.substring(7);

        // 获取 请求头 uuid
        String uuid = request.getHeader(authProperties.getUuidKey());
        if (StrUtil.isBlank(uuid)) {
            log.error("请求头中的 uuid 不存在");
            throw AuthBusinessException.authTokenVerifyFailed();
        }

        // 获取 redis 缓存中的 token 信息
        Object loginUserObj = redisUtil.get(AuthRedisConsts.JWT_PREFIX + uuid);
        if (Objects.isNull(loginUserObj)) {
            log.error("redis 中无登录信息:【{}】", AuthRedisConsts.JWT_PREFIX + uuid);
            throw AuthBusinessException.authTokenVerifyFailed();
        }
        LoginUser loginUser;
        if (loginUserObj instanceof LoginUser) {
            loginUser = (LoginUser) loginUserObj;
        } else {
            log.error("redis 中的登录信息不正确:【{}】", JSONUtil.toJsonStr(loginUserObj));
            throw AuthBusinessException.authTokenVerifyFailed();
        }

        // 判断请求头与 redis 中的 uuid 是否相同
        if (!StrUtil.equals(loginUser.getTokenUuid(), request.getHeader(authProperties.getUuidKey()))) {
            log.error("请求头与 redis 中的 uuid 不相同. 请求头 uuid:【{}】,redis uuid:【{}】",
                request.getHeader(authProperties.getUuidKey()), loginUser.getTokenUuid());
            throw AuthBusinessException.authTokenVerifyFailed();
        }

        // 验证 token
        tokenBusiness.verifyToken(token, loginUser.getJwtSecret());
        // 设置内部请求参数, 保存登录信息
        request.setAttribute(AuthConsts.LOGIN_USER_REQUEST_HEADER_KEY, loginUser);
        // 刷新 token
        redisUtil.expire(AuthRedisConsts.JWT_PREFIX + loginUser.getTokenUuid(), authProperties.getAccessExpire() * 60);
        log.info("loginUser:{}", JSONUtil.toJsonStr(loginUser));
        return true;
    }


    /**
     * 匿名访问处理.
     */
    private static boolean anonymouseHandler(HttpServletRequest request, HandlerMethod handler) {
        AnonymousAccess anonymousAccess = handler.getMethod()
            .getAnnotation(AnonymousAccess.class);
        if (Objects.nonNull(anonymousAccess)) {
            log.info("{} Anonymous", request.getRequestURI());
            return true;
        }
        return false;
    }

    /**
     * 不处理登录,注册相关请求.
     */
    private static boolean matchIgnoreUrls(HttpServletRequest request) {
        String[] ignoreUrls = {"/auth/login"};
        boolean matchIgnoreUrls = false;
        for (String url : ignoreUrls) {
            if (StrUtil.equals(request.getRequestURI(), url)) {
                matchIgnoreUrls = true;
                break;
            }
        }
        return matchIgnoreUrls;
    }
}
